This script reads in both PBMC and Liver Seurat objects, and
generates select figures used in the manuscript.
Prepare objects
Load libraries
library(Seurat)
Loading required package: SeuratObject
Loading required package: sp
Attaching package: ‘SeuratObject’
The following objects are masked from ‘package:base’:
intersect, saveRDS
Loading Seurat v5 beta version
To maintain compatibility with previous workflows, new Seurat objects will use the previous object structure by default
To use new Seurat v5 assays: Please run: options(Seurat.object.assay.version = 'v5')
library(scClustViz)
Loading required package: shiny
library(ggplot2)
library(dplyr)
Attaching package: ‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
library(rcartocolor)
library(SeuratWrappers)
Read in liver map
sobj <- readRDS("~/Dropbox/Zoe/scf_version/analysis/healthy_sc/seurat_objects/dropletQC_filtered/allIntegrated_cca_kanchor5_noBiopsyHeps_dropletQCFiltered.RDS")
res <- "integrated_snn_res.1.4"
Idents(sobj) <- res
tissue <- "liver"
Read in PBMC map
load("~/Dropbox/Zoe/scf_version/analysis/healthy_sc/seurat_objects/no_dropletQC/integrated_PBMC_cca_kanchor5_scClustViz.RData")
sobj <- scSeurat
res <- "integrated_snn_res.0.6"
Idents(sobj) <- res
tissue <- "PBMC"
Visualization of metadata
UMAP with no cluster numbers
plot <- DimPlot(sobj, label = FALSE) & NoLegend()
plot
pdf(paste("./figures/", tissue, "/", tissue, "_UMAP_clusters_noLabels.pdf", sep = ""))
plot
dev.off()
png
2

UMAP with cluster numbers
plot <- DimPlot(sobj, label = TRUE)
plot
pdf(paste("./figures/", tissue, "/", tissue, "_UMAP_clusters_labels.pdf", sep = ""))
plot
dev.off()
png
2

Map with SCINA-generated cell-type labels
DimPlot(sobj, group.by = "scina_labels_refined", label = TRUE) & NoLegend()

Map with general cell-type labels for paper
plot <- DimPlot(sobj, group.by = "general_cell_labels",
label = TRUE, repel = TRUE) +
ggtitle(NULL)
plot
pdf(paste("./figures/", tissue, "/", tissue, "_UMAP_general_cell_labels.pdf", sep = ""),
height = 7,
width = 9)
plot
dev.off()
png
2

Map grouping by general cell type labels but with no labels on
plot
plot <- DimPlot(sobj, group.by = "general_cell_labels") +
ggtitle(NULL) &
NoLegend()
plot
pdf(paste("./figures/", tissue, "/", tissue, "_UMAP_general_cell_labels_noLabels.pdf",
sep = ""))
plot
dev.off()
png
2

Map with original identities
plot <- DimPlot(sobj, group.by = "orig.ident",
cols = carto_pal(length(levels(as.factor(sobj$orig.ident))), "Safe")) +
ggtitle(NULL)
plot
pdf(paste("./figures/", tissue, "/", tissue, "_UMAP_orig_idents.pdf",
sep = ""))
plot
dev.off()
png
2

Barplot with original identities on a cluster-level grouping:
# Meta data to plot:
df <- sobj@meta.data
# Check what column the cluster identities are in
col <- which(colnames(df) == res)
# Order clusters
df[,col] <- factor(Idents(sobj),
levels = c(sort(as.numeric(levels(Idents(sobj))))))
# Basic plot of clusters by replicate
ggplot(df, aes(x = get(res), fill = orig.ident)) +
geom_bar() +
theme(axis.text = element_text(size = 7))

# Plot as proportion or percentage of cluster
ggplot(df, aes(x = get(res), fill = orig.ident)) +
geom_bar(position = "fill") +
theme(axis.text = element_text(size = 7))

Barplot with original identities on a grouped by general cell
labels:
df <- sobj@meta.data
plot1 <- ggplot(df, aes(x = general_cell_labels, fill = orig.ident)) +
geom_bar() +
scale_fill_carto_d(name = NULL, palette = "Safe") +
theme_bw() +
theme(axis.text = element_text(size = 8),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
axis.title.x = element_blank()) +
ylab("Number of cells")
plot1
pdf(paste("./figures/", tissue, "/", tissue, "_barplot_orig_ident_counts.pdf",
sep = ""))
plot1
dev.off()
png
2

# Plot as proportion or percentage of cluster
plot2 <- ggplot(df, aes(x = general_cell_labels, fill = orig.ident)) +
geom_bar(position = "fill") +
scale_fill_carto_d(name = NULL, palette = "Safe") +
theme_bw() +
theme(axis.text = element_text(size = 8),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
axis.title.x = element_blank()) +
ylab("Proportion of cells")
plot2
pdf(paste("./figures/", tissue, "/", tissue, "_barplot_orig_ident_proportions.pdf",
sep = ""))
plot2
dev.off()
png
2

Dotplots
Generate dotplot with specific markers
DotPlot(sobj,
assay = "SCT",
features = c("PTPRC", "CALCRL", "NKG7", "CD3E", "MARCO", "LYZ-1", "CD19", "MS4A1", "STAB2")
) +
ggtitle("Select features for liver map")
Heatmaps
Calculate markers for general cell labels then reset resolution
Idents(sobj) <- "general_cell_labels"
sobj_markers <- RunPrestoAll(sobj,
only.pos = TRUE,
min.pct = 0.25,
logfc.threshold = 0.25)
Calculating cluster B cells
Calculating cluster Lymphocytes 3
Calculating cluster Monocytes 1
Calculating cluster Erythrocytes
Calculating cluster Lymphocytes 1
Calculating cluster Monocytes 2
Calculating cluster Lymphocytes 2
Calculating cluster Lymphocytes 5
Calculating cluster Monocytes 3
Calculating cluster Monocytes 4
Calculating cluster Plasma B cells
Calculating cluster Unknown
Calculating cluster Mast cells
Calculating cluster Lymphocytes 4
Calculating cluster Hematopoietic stem cells
Calculating cluster Megakaryocytes
sobj_markers %>%
group_by(cluster) %>%
slice_max(n = 2, order_by = avg_log2FC)
Idents(sobj) <- res
Save markers
groups = "general_cell_labels"
write.table(sobj_markers,
file = paste("./figures/", tissue, "/",
tissue, "_markers_", groups, ".tsv",
sep = ""),
quote = FALSE,
sep = "\t",
row.names = FALSE,
col.names = TRUE)
Generate heatmap with top 5 markers grouping by general cell
types
# Remove mikado genes from marker list
sobj_markers <- sobj_markers[grep("mikado", rownames(sobj_markers), invert = TRUE),]
sobj_markers %>%
group_by(cluster) %>%
top_n(n = 5, wt = avg_log2FC) -> top
# If liver, select fewer cells
if (tissue == "liver") {
cells <- sample(colnames(sobj), size = 30000)
} else if (tissue == "PBMC") {
cells <- colnames(sobj)
}
DoHeatmap(sobj, features = top$gene, group.by = "general_cell_labels", size = 3,
angle = 90, cells = cells) +
NoLegend() +
theme(text = element_text(size = 7))

Make PDF of heatmap
groups <- "general_cell_labels"
pdf(paste("./figures/", tissue, "/", tissue, "_heatmap_", groups, ".pdf", sep = ""),
height = 11,
width = 7)
DoHeatmap(sobj, features = top$gene, group.by = "general_cell_labels", size = 2,
angle = 90, cells = cells) +
NoLegend() +
theme(text = element_text(size = 7))
dev.off()
null device
1
Feature plots
Make specific plots with specific genes. The genes we are interested
in include: PTPRC, CALCRL, NKG7, CD3E, MARCO, LYZ, CD19, MS4A1,
STAB2, ALB, CD4, CD8A, CLEC4G, CD5L, C1QB, ACTA2, VWF, IGLL5, CD68.
Can also plot in italics.
geneCode <- "sct_LYZ-1" # Woodchuck-specific nomenclature for this genome
gene <- "LYZ"
mapType <- "Liver"
FeaturePlot(sobj, features = geneCode) +
ggtitle(paste(gene, "-", mapType, "map"))

FeaturePlot(sobj, features = geneCode) +
ggtitle(bquote(~italic(.(gene))))

Another version of the feature plot that outputs genes in italics
if (tissue == "PBMC") {
geneCodes <- c("sct_PTPRC","sct_NKG7","sct_CD14",
"sct_CD3E","sct_MARCO","sct_LYZ-1",
"sct_CD19","sct_MS4A1","sct_STAB2",
"sct_CD4","sct_CD8A", "sct_XCL1;XCL2",
"sct_CD5L","sct_C1QB", "sct_LEF1",
"sct_ACTA2","sct_VWF","sct_IGLL5-1",
"sct_CD68","sct_FCGR3A;FCGR3B","sct_TOP2A")
genes <- c("PTPRC","NKG7","CD14","CD3E","MARCO","LYZ",
"CD19","MS4A1","STAB2","CD4","CD8A", "XCL1;XCL2",
"CD5L","C1QB","LEF1","ACTA2","VWF","IGLL5","CD68",
"FCGR3A;FCGR3B","TOP2A")
} else if (tissue == "liver") {
geneCodes <- c("sct_Ptprc","sct_CALCRL","sct_NKG7",
"sct_CD3E","sct_MARCO","sct_LYZ-1",
"sct_CD19","sct_MS4A1","sct_STAB2",
"sct_ALB-1","sct_CD4","sct_CD8A",
"sct_CLEC4G","sct_CD5L","sct_C1QB",
"sct_ACTA2","sct_VWF","sct_IGLL5-1",
"sct_CD68")
genes <- c("PTPRC","CALCRL","NKG7","CD3E","MARCO","LYZ",
"CD19","MS4A1","STAB2","ALB","CD4","CD8A",
"CLEC4G","CD5L","C1QB","ACTA2","VWF","IGLL5","CD68")
}
for(num in 2:length(geneCodes)) {
plot <- FeaturePlot(sobj,
features = geneCodes[num]) +
ggtitle(bquote(~italic(.(genes[num]))))
print(plot)
pdf(paste("./figures/", tissue, "/", tissue, "_", genes[num], "_UMAP.pdf", sep = ""))
print(plot)
dev.off()
}




















Correlation plots
Generate heatmaps comparing woodchuck clusters with various
datasets
Setup for all correlations
First, set up whatever woodchuck dataset I am working with by reading
in the ortholog table, choosing whether the orthologs to use are human,
mouse, or woodchuck, and calculating the average expression for each
cluster. The output of this section is the scaled cluster
gene-expression matrix
# Read in ortholog table
geneNameTable <- read.table("~/Dropbox/Zoe/scf_version/make_gtf/orthofinder_sc2/homologene/collectedOrthofinderPairings.tsv",
sep = "\t",
header = TRUE)
woodchuckClusterAverages <- AverageExpression(sobj,
assays = "SCT",
slot = "scale.data")
# Scale data
woodchuckClusterAverages$SCT <- na.omit(t(scale(t(woodchuckClusterAverages$SCT))))
# Grab gene names from Seurat object
uniqueHier <- row.names(woodchuckClusterAverages$SCT)
uniqueHier <- as.data.frame(uniqueHier)
# Bind with geneNameTable to get correct order (notice uniqueHier is on left)
newNames <- dplyr::left_join(uniqueHier, geneNameTable, by = "uniqueHier")
# Get orthologs from either mouse or human
species <- "human"
if (species == "human") {
# If human one-to-one ortho has NA, replace with mikado_final_sc2_stringent_noMito_protein column
# This is to avoid and potential mistakes in recognizing things it shouldn't be recognizing
newNames$speciesOneToOne <- ifelse(is.na(newNames$humanOneToOne), newNames$uniqueHier, newNames$humanOneToOne)
} else if (species == "mouse") {
newNames$speciesOneToOne <- ifelse(is.na(newNames$mouseOneToOne), newNames$uniqueHier, newNames$mouseOneToOne)
} else if (species == "woodchuck") {
newNames$speciesOneToOne <- newNames$uniqueHier
}
# Grab dataframe
woodchuckClusterAverages <- woodchuckClusterAverages$SCT
# Replace names with one-to-one orthologue of particular species
row.names(woodchuckClusterAverages) <- newNames$speciesOneToOne
# Make sure formatted correctly
woodchuckClusterAverages <- as.data.frame(woodchuckClusterAverages)
# Order by gene name
woodchuckClusterAverages <- woodchuckClusterAverages[order(row.names(woodchuckClusterAverages)),]
Dataset-specific setup
Correlation of woodchuck PBMCs with human 68k PBMC dataset from 10X
Genomics
# Now need to read in 68K PBMC data
humanPBMC <- read.csv("~/Dropbox/Zoe/scf_version/analysis/correlationTests/68K_pbmc_data/68K_enrichedGenes.csv",
header = FALSE)
# Get rid of top row
humanPBMC <- humanPBMC[-1,]
# Separate all cell and myloid cell data
allCells <- select(humanPBMC, V1, V2, V3)
myeloid <- select(humanPBMC, V5, V6, V7)
# Rename and get rid of first row
colnames(allCells) <- c("Cluster", "Gene", "Enrichment")
colnames(myeloid) <- c("Cluster", "Gene", "Enrichment")
# Get rid of top row
allCells <- allCells[-1,]
myeloid <- myeloid[-1,]
# I can then filter the rows and bind the data frames back together by gene name
for (j in 1:10) {
dat <- dplyr::filter(allCells, Cluster == j)
#dat <- get(paste("allCells", j, sep = ""))
# Multiply enrichment values by -1 because the signs are backwards???
dat$Enrichment <- as.numeric(dat$Enrichment) * -1
dat <- dplyr::select(dat, Gene, Enrichment)
colnames(dat) <- c("Gene", paste("Enrichment", j, sep = ""))
if (j == 1) {
allCellsMatrix <- dat
}
else {
allCellsMatrix <- dplyr::full_join(allCellsMatrix, dat, by = "Gene")
}
}
# Make row names gene names
rownames(allCellsMatrix) <- allCellsMatrix$Gene
allCellsMatrix <- dplyr::select(allCellsMatrix, -Gene)
# Make column names cell types
colnames(allCellsMatrix) <- c("Activated CD8+", "Naive CD8+", "Memory and Reg T",
"Naive CD4+", "NK", "CD8+", "B", "Megakaryocytes",
"Monocytes and Dendritic", "B, Dendritic, T")
# Scale across columns
allCellsMatrix <- t(scale(t(allCellsMatrix)))
# Order genes alphabetically by gene name
allCellsMatrix <- allCellsMatrix[order(row.names(allCellsMatrix)),]
Correlation of woodchuck liver with human liver dataset from
MacParland et al. (2018)
# Find cluster averages of human liver data
load("~/Dropbox/Zoe/scf_version/analysis/correlationTests/HumanLiver.RData")
# Run SCTransform
HumanLiverSeurat <- UpdateSeuratObject(HumanLiverSeurat)
HumanLiverSeurat <- SCTransform(HumanLiverSeurat)
humanClusterAverages <- AverageExpression(HumanLiverSeurat,
assays = "SCT",
slot = "scale.data")
# Replace cluster numbers with names
colnames(humanClusterAverages$SCT) <- c("Hep 1", "Alpha-beta T cells", "Hep 2",
"Inflammatory macs", "Hep 3", "Hep 4",
"Plasma cells", "NK-like cells", "Gamma-delta T cells",
"Non-inflammatory macs", "Periportal LSECs", "Central venous LSECs",
"Portal endothelial cells", "Hep 5", "Hep 6",
"Mature B cells", "Cholangiocytes", "Gamma-delta T cells 2",
"Erythroid cells", "Hepatic stellate cells")
# If only looking at specific clusters
#humanClusterAverages$SCT <- humanClusterAverages$SCT[,c("3","1","15","6","14","5")]
# Otherwise go straight to here:
humanClusterAverages$SCT <- na.omit(t(scale(t(humanClusterAverages$SCT))))
# Grab gene names
humanGenes <- row.names(humanClusterAverages$SCT)
# Now turn into large dataframe
allCellsMatrix <- as.data.frame(humanClusterAverages$SCT)
# Order by row name
allCellsMatrix <- allCellsMatrix[order(row.names(allCellsMatrix)),]
Correlation of woodchuck liver with human liver dataset from Aizarani
et al.
# Read in Aizarani dataset
aizarani <- readRDS("~/Dropbox/Zoe/scf_version/analysis/correlationTests/GSE124395_Normalhumanliverdata.RData")
# Read in clusters and label cells
aizaraniClusters <- read.table("~/Dropbox/Zoe/scf_version/analysis/correlationTests/GSE124395_clusterpartition.txt")
# Only keep cells in the cluster object
aizarani <- aizarani[,intersect(colnames(aizarani),row.names(aizaraniClusters))]
# Create Seurat object
aizarani <- CreateSeuratObject(counts = aizarani)
# Run SCTransform
aizarani <- SCTransform(aizarani)
# Add cluster IDs
Idents(aizarani) <- aizaraniClusters$sct.cpart
# Get cluster averages
aizaraniAverages <- AverageExpression(aizarani,
assays = "SCT",
slot = "scale.data")
aizaraniAverages$SCT <- na.omit(t(scale(t(aizaraniAverages$SCT))))
# Grab gene names
aizaraniGenes <- row.names(aizaraniAverages$SCT)
# Now turn into large dataframe
allCellsMatrix <- as.data.frame(aizaraniAverages$SCT)
# Order by row name
allCellsMatrix <- allCellsMatrix[order(row.names(allCellsMatrix)),]
Correlation of woodchuck liver with woodchuck PBMCs. For this
correlation, read in the woodchuck liver dataset at the beginning of
this script and then read in the woodchuck PBMCs below
# Start with liver and read in woodchuck PBMCs again
load("~/Dropbox/Zoe/scf_version/analysis/healthy_sc/seurat_objects/no_dropletQC/integrated_PBMC_cca_kanchor5_scClustViz.RData")
Idents(scSeurat) <- "integrated_snn_res.0.6"
# Find cluster averages
pbmcClusterAverages <- AverageExpression(scSeurat,
assays = "SCT",
slot = "scale.data")
pbmcClusterAverages <- as.data.frame(na.omit(t(scale(t(pbmcClusterAverages$SCT)))))
# Order by row name
allCellsMatrix <- pbmcClusterAverages[order(row.names(pbmcClusterAverages)),]
Output plots for all correlations
speciesData <- "Human Liver (Aizarani et al.)"
woodchuckData <- "Woodchuck Liver"
# Now find intersecting genes
matches <- intersect(row.names(allCellsMatrix),
row.names(woodchuckClusterAverages))
# Look at how many genes matched
length(matches)
# Make new matrices with only matching gene names
toCor <- allCellsMatrix[matches,]
woodchuckAveragesCor <- woodchuckClusterAverages[matches,]
# Do Pearson
pearVal <- cor(toCor, woodchuckAveragesCor, method = "pearson")
heatmap(pearVal,
main = paste("Pearson correlation of", speciesData, "vs", woodchuckData),
xlab = woodchuckData,
ylab = speciesData)
#margins = c(6,11))
#Rowv = NA,
#Colv = NA)
# Do Spearman
spearVal <- cor(toCor, woodchuckAveragesCor, method = "spearman")
heatmap(spearVal,
main = paste("Spearman correlation of", speciesData, "vs", woodchuckData),
xlab = woodchuckData,
ylab = speciesData)
#margins = c(6,11))
#Rowv = NA,
#Colv = NA)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBzY3JpcHQgcmVhZHMgaW4gYm90aCBQQk1DIGFuZCBMaXZlciBTZXVyYXQgb2JqZWN0cywgYW5kIGdlbmVyYXRlcyBzZWxlY3QgZmlndXJlcyB1c2VkIGluIHRoZSBtYW51c2NyaXB0LgoKIyMgUHJlcGFyZSBvYmplY3RzCgpMb2FkIGxpYnJhcmllcwoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KHNjQ2x1c3RWaXopCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkcGx5cikKbGlicmFyeShyY2FydG9jb2xvcikKbGlicmFyeShTZXVyYXRXcmFwcGVycykKYGBgCgpSZWFkIGluIGxpdmVyIG1hcAoKYGBge3J9CnNvYmogPC0gcmVhZFJEUygifi9Ecm9wYm94L1pvZS9zY2ZfdmVyc2lvbi9hbmFseXNpcy9oZWFsdGh5X3NjL3NldXJhdF9vYmplY3RzL2Ryb3BsZXRRQ19maWx0ZXJlZC9hbGxJbnRlZ3JhdGVkX2NjYV9rYW5jaG9yNV9ub0Jpb3BzeUhlcHNfZHJvcGxldFFDRmlsdGVyZWQuUkRTIikKcmVzIDwtICJpbnRlZ3JhdGVkX3Nubl9yZXMuMS40IgpJZGVudHMoc29iaikgPC0gcmVzCnRpc3N1ZSA8LSAibGl2ZXIiCmBgYAoKUmVhZCBpbiBQQk1DIG1hcAoKYGBge3J9CmxvYWQoIn4vRHJvcGJveC9ab2Uvc2NmX3ZlcnNpb24vYW5hbHlzaXMvaGVhbHRoeV9zYy9zZXVyYXRfb2JqZWN0cy9ub19kcm9wbGV0UUMvaW50ZWdyYXRlZF9QQk1DX2NjYV9rYW5jaG9yNV9zY0NsdXN0Vml6LlJEYXRhIikKc29iaiA8LSBzY1NldXJhdApyZXMgPC0gImludGVncmF0ZWRfc25uX3Jlcy4wLjYiCklkZW50cyhzb2JqKSA8LSByZXMKdGlzc3VlIDwtICJQQk1DIgpgYGAKCiMjIFZpc3VhbGl6YXRpb24gb2YgbWV0YWRhdGEKClVNQVAgd2l0aCBubyBjbHVzdGVyIG51bWJlcnMKCmBgYHtyfQpwbG90IDwtIERpbVBsb3Qoc29iaiwgbGFiZWwgPSBGQUxTRSkgJiBOb0xlZ2VuZCgpCnBsb3QKcGRmKHBhc3RlKCIuL2ZpZ3VyZXMvIiwgdGlzc3VlLCAiLyIsIHRpc3N1ZSwgIl9VTUFQX2NsdXN0ZXJzX25vTGFiZWxzLnBkZiIsIHNlcCA9ICIiKSkKcGxvdApkZXYub2ZmKCkKYGBgCgpVTUFQIHdpdGggY2x1c3RlciBudW1iZXJzCgpgYGB7cn0KcGxvdCA8LSBEaW1QbG90KHNvYmosIGxhYmVsID0gVFJVRSkKcGxvdApwZGYocGFzdGUoIi4vZmlndXJlcy8iLCB0aXNzdWUsICIvIiwgdGlzc3VlLCAiX1VNQVBfY2x1c3RlcnNfbGFiZWxzLnBkZiIsIHNlcCA9ICIiKSkKcGxvdApkZXYub2ZmKCkKYGBgCgpNYXAgd2l0aCBTQ0lOQS1nZW5lcmF0ZWQgY2VsbC10eXBlIGxhYmVscwoKYGBge3J9CkRpbVBsb3Qoc29iaiwgZ3JvdXAuYnkgPSAic2NpbmFfbGFiZWxzX3JlZmluZWQiLCBsYWJlbCA9IFRSVUUpICYgTm9MZWdlbmQoKQpgYGAKCk1hcCB3aXRoIGdlbmVyYWwgY2VsbC10eXBlIGxhYmVscyBmb3IgcGFwZXIKCmBgYHtyfQpwbG90IDwtIERpbVBsb3Qoc29iaiwgZ3JvdXAuYnkgPSAiZ2VuZXJhbF9jZWxsX2xhYmVscyIsCiAgICAgICAgbGFiZWwgPSBUUlVFLCByZXBlbCA9IFRSVUUpICsKICBnZ3RpdGxlKE5VTEwpCnBsb3QKcGRmKHBhc3RlKCIuL2ZpZ3VyZXMvIiwgdGlzc3VlLCAiLyIsIHRpc3N1ZSwgIl9VTUFQX2dlbmVyYWxfY2VsbF9sYWJlbHMucGRmIiwgc2VwID0gIiIpLAogICAgaGVpZ2h0ID0gNywKICAgIHdpZHRoID0gOSkKcGxvdApkZXYub2ZmKCkKYGBgCgpNYXAgZ3JvdXBpbmcgYnkgZ2VuZXJhbCBjZWxsIHR5cGUgbGFiZWxzIGJ1dCB3aXRoIG5vIGxhYmVscyBvbiBwbG90CgpgYGB7cn0KcGxvdCA8LSBEaW1QbG90KHNvYmosIGdyb3VwLmJ5ID0gImdlbmVyYWxfY2VsbF9sYWJlbHMiKSArCiAgZ2d0aXRsZShOVUxMKSAmCiAgTm9MZWdlbmQoKQpwbG90CnBkZihwYXN0ZSgiLi9maWd1cmVzLyIsIHRpc3N1ZSwgIi8iLCB0aXNzdWUsICJfVU1BUF9nZW5lcmFsX2NlbGxfbGFiZWxzX25vTGFiZWxzLnBkZiIsIAogICAgICAgICAgc2VwID0gIiIpKQpwbG90CmRldi5vZmYoKQpgYGAKCk1hcCB3aXRoIG9yaWdpbmFsIGlkZW50aXRpZXMKCmBgYHtyfQpwbG90IDwtIERpbVBsb3Qoc29iaiwgZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIsCiAgICAgICAgY29scyA9IGNhcnRvX3BhbChsZW5ndGgobGV2ZWxzKGFzLmZhY3Rvcihzb2JqJG9yaWcuaWRlbnQpKSksICJTYWZlIikpICsKICBnZ3RpdGxlKE5VTEwpCnBsb3QKcGRmKHBhc3RlKCIuL2ZpZ3VyZXMvIiwgdGlzc3VlLCAiLyIsIHRpc3N1ZSwgIl9VTUFQX29yaWdfaWRlbnRzLnBkZiIsIAogICAgICAgICAgc2VwID0gIiIpKQpwbG90CmRldi5vZmYoKQpgYGAKCkJhcnBsb3Qgd2l0aCBvcmlnaW5hbCBpZGVudGl0aWVzIG9uIGEgY2x1c3Rlci1sZXZlbCBncm91cGluZzoKCmBgYHtyfQojIE1ldGEgZGF0YSB0byBwbG90OgpkZiA8LSBzb2JqQG1ldGEuZGF0YQojIENoZWNrIHdoYXQgY29sdW1uIHRoZSBjbHVzdGVyIGlkZW50aXRpZXMgYXJlIGluCmNvbCA8LSB3aGljaChjb2xuYW1lcyhkZikgPT0gcmVzKQojIE9yZGVyIGNsdXN0ZXJzCmRmWyxjb2xdIDwtIGZhY3RvcihJZGVudHMoc29iaiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYyhzb3J0KGFzLm51bWVyaWMobGV2ZWxzKElkZW50cyhzb2JqKSkpKSkpCiMgQmFzaWMgcGxvdCBvZiBjbHVzdGVycyBieSByZXBsaWNhdGUKZ2dwbG90KGRmLCBhZXMoeCA9IGdldChyZXMpLCBmaWxsID0gb3JpZy5pZGVudCkpICsKICBnZW9tX2JhcigpICsKICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpKQojIFBsb3QgYXMgcHJvcG9ydGlvbiBvciBwZXJjZW50YWdlIG9mIGNsdXN0ZXIKZ2dwbG90KGRmLCBhZXMoeCA9IGdldChyZXMpLCBmaWxsID0gb3JpZy5pZGVudCkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNykpCmBgYAoKQmFycGxvdCB3aXRoIG9yaWdpbmFsIGlkZW50aXRpZXMgb24gYSBncm91cGVkIGJ5IGdlbmVyYWwgY2VsbCBsYWJlbHM6CgpgYGB7cn0KZGYgPC0gc29iakBtZXRhLmRhdGEKcGxvdDEgPC0gZ2dwbG90KGRmLCBhZXMoeCA9IGdlbmVyYWxfY2VsbF9sYWJlbHMsIGZpbGwgPSBvcmlnLmlkZW50KSkgKwogIGdlb21fYmFyKCkgKwogIHNjYWxlX2ZpbGxfY2FydG9fZChuYW1lID0gTlVMTCwgcGFsZXR0ZSA9ICJTYWZlIikgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHlsYWIoIk51bWJlciBvZiBjZWxscyIpCnBsb3QxCnBkZihwYXN0ZSgiLi9maWd1cmVzLyIsIHRpc3N1ZSwgIi8iLCB0aXNzdWUsICJfYmFycGxvdF9vcmlnX2lkZW50X2NvdW50cy5wZGYiLCAKICAgICAgICAgIHNlcCA9ICIiKSkKcGxvdDEKZGV2Lm9mZigpCiMgUGxvdCBhcyBwcm9wb3J0aW9uIG9yIHBlcmNlbnRhZ2Ugb2YgY2x1c3RlcgpwbG90MiA8LSBnZ3Bsb3QoZGYsIGFlcyh4ID0gZ2VuZXJhbF9jZWxsX2xhYmVscywgZmlsbCA9IG9yaWcuaWRlbnQpKSArCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICBzY2FsZV9maWxsX2NhcnRvX2QobmFtZSA9IE5VTEwsIHBhbGV0dGUgPSAiU2FmZSIpICsKICB0aGVtZV9idygpICsKICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB5bGFiKCJQcm9wb3J0aW9uIG9mIGNlbGxzIikKcGxvdDIKcGRmKHBhc3RlKCIuL2ZpZ3VyZXMvIiwgdGlzc3VlLCAiLyIsIHRpc3N1ZSwgIl9iYXJwbG90X29yaWdfaWRlbnRfcHJvcG9ydGlvbnMucGRmIiwgCiAgICAgICAgICBzZXAgPSAiIikpCnBsb3QyCmRldi5vZmYoKQpgYGAKCiMjIERvdHBsb3RzCgpHZW5lcmF0ZSBkb3RwbG90IHdpdGggc3BlY2lmaWMgbWFya2VycwoKYGBge3J9CkRvdFBsb3Qoc29iaiwKICAgICAgICBhc3NheSA9ICJTQ1QiLAogICAgICAgIGZlYXR1cmVzID0gYygiUFRQUkMiLCAiQ0FMQ1JMIiwgIk5LRzciLCAiQ0QzRSIsICJNQVJDTyIsICJMWVotMSIsICJDRDE5IiwgIk1TNEExIiwgIlNUQUIyIikKICAgICAgICApICsKICBnZ3RpdGxlKCJTZWxlY3QgZmVhdHVyZXMgZm9yIGxpdmVyIG1hcCIpCmBgYAoKIyMgSGVhdG1hcHMKCkNhbGN1bGF0ZSBtYXJrZXJzIGZvciBnZW5lcmFsIGNlbGwgbGFiZWxzIHRoZW4gcmVzZXQgcmVzb2x1dGlvbgoKYGBge3J9CklkZW50cyhzb2JqKSA8LSAiZ2VuZXJhbF9jZWxsX2xhYmVscyIKc29ial9tYXJrZXJzIDwtIFJ1blByZXN0b0FsbChzb2JqLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmx5LnBvcyA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5wY3QgPSAwLjI1LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dmYy50aHJlc2hvbGQgPSAwLjI1KQpzb2JqX21hcmtlcnMgJT4lCiAgICBncm91cF9ieShjbHVzdGVyKSAlPiUKICAgIHNsaWNlX21heChuID0gMiwgb3JkZXJfYnkgPSBhdmdfbG9nMkZDKQpJZGVudHMoc29iaikgPC0gcmVzCmBgYAoKU2F2ZSBtYXJrZXJzCgpgYGB7cn0KZ3JvdXBzID0gImdlbmVyYWxfY2VsbF9sYWJlbHMiCndyaXRlLnRhYmxlKHNvYmpfbWFya2VycywKICAgICAgICAgICAgZmlsZSA9IHBhc3RlKCIuL2ZpZ3VyZXMvIiwgdGlzc3VlLCAiLyIsCiAgICAgICAgICAgICAgICAgICAgICAgICB0aXNzdWUsICJfbWFya2Vyc18iLCBncm91cHMsICIudHN2IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIiksCiAgICAgICAgICAgIHF1b3RlID0gRkFMU0UsCiAgICAgICAgICAgIHNlcCA9ICJcdCIsCiAgICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFLAogICAgICAgICAgICBjb2wubmFtZXMgPSBUUlVFKQpgYGAKCkdlbmVyYXRlIGhlYXRtYXAgd2l0aCB0b3AgNSBtYXJrZXJzIGdyb3VwaW5nIGJ5IGdlbmVyYWwgY2VsbCB0eXBlcwoKYGBge3J9CiMgUmVtb3ZlIG1pa2FkbyBnZW5lcyBmcm9tIG1hcmtlciBsaXN0CnNvYmpfbWFya2VycyA8LSBzb2JqX21hcmtlcnNbZ3JlcCgibWlrYWRvIiwgcm93bmFtZXMoc29ial9tYXJrZXJzKSwgaW52ZXJ0ID0gVFJVRSksXQpzb2JqX21hcmtlcnMgJT4lCiAgICBncm91cF9ieShjbHVzdGVyKSAlPiUKICAgIHRvcF9uKG4gPSA1LCB3dCA9IGF2Z19sb2cyRkMpIC0+IHRvcAojIElmIGxpdmVyLCBzZWxlY3QgZmV3ZXIgY2VsbHMKaWYgKHRpc3N1ZSA9PSAibGl2ZXIiKSB7CiAgY2VsbHMgPC0gc2FtcGxlKGNvbG5hbWVzKHNvYmopLCBzaXplID0gMzAwMDApCn0gZWxzZSBpZiAodGlzc3VlID09ICJQQk1DIikgewogIGNlbGxzIDwtIGNvbG5hbWVzKHNvYmopCn0KRG9IZWF0bWFwKHNvYmosIGZlYXR1cmVzID0gdG9wJGdlbmUsIGdyb3VwLmJ5ID0gImdlbmVyYWxfY2VsbF9sYWJlbHMiLCBzaXplID0gMywgCiAgICAgICAgICBhbmdsZSA9IDkwLCBjZWxscyA9IGNlbGxzKSArCiAgTm9MZWdlbmQoKSArCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNykpCmBgYAoKTWFrZSBQREYgb2YgaGVhdG1hcAoKYGBge3J9Cmdyb3VwcyA8LSAiZ2VuZXJhbF9jZWxsX2xhYmVscyIKcGRmKHBhc3RlKCIuL2ZpZ3VyZXMvIiwgdGlzc3VlLCAiLyIsIHRpc3N1ZSwgIl9oZWF0bWFwXyIsIGdyb3VwcywgIi5wZGYiLCBzZXAgPSAiIiksCiAgICBoZWlnaHQgPSAxMSwKICAgIHdpZHRoID0gNykKRG9IZWF0bWFwKHNvYmosIGZlYXR1cmVzID0gdG9wJGdlbmUsIGdyb3VwLmJ5ID0gImdlbmVyYWxfY2VsbF9sYWJlbHMiLCBzaXplID0gMiwgCiAgICAgICAgICBhbmdsZSA9IDkwLCBjZWxscyA9IGNlbGxzKSArCiAgTm9MZWdlbmQoKSArCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNykpCmRldi5vZmYoKQpgYGAKCgojIyBGZWF0dXJlIHBsb3RzCgpNYWtlIHNwZWNpZmljIHBsb3RzIHdpdGggc3BlY2lmaWMgZ2VuZXMuIFRoZSBnZW5lcyB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBpbmNsdWRlOiAqUFRQUkMsIENBTENSTCwgTktHNywgQ0QzRSwgTUFSQ08sIExZWiwgQ0QxOSwgTVM0QTEsIFNUQUIyLCBBTEIsIENENCwgQ0Q4QSwgQ0xFQzRHLCBDRDVMLCBDMVFCLCBBQ1RBMiwgVldGLCBJR0xMNSwgQ0Q2OCouIENhbiBhbHNvIHBsb3QgaW4gaXRhbGljcy4KCmBgYHtyfQpnZW5lQ29kZSA8LSAic2N0X0xZWi0xIiAjIFdvb2RjaHVjay1zcGVjaWZpYyBub21lbmNsYXR1cmUgZm9yIHRoaXMgZ2Vub21lCmdlbmUgPC0gIkxZWiIKbWFwVHlwZSA8LSAiTGl2ZXIiCkZlYXR1cmVQbG90KHNvYmosIGZlYXR1cmVzID0gZ2VuZUNvZGUpICsKICBnZ3RpdGxlKHBhc3RlKGdlbmUsICItIiwgbWFwVHlwZSwgIm1hcCIpKQpGZWF0dXJlUGxvdChzb2JqLCBmZWF0dXJlcyA9IGdlbmVDb2RlKSArCiAgZ2d0aXRsZShicXVvdGUofml0YWxpYyguKGdlbmUpKSkpCmBgYAoKQW5vdGhlciB2ZXJzaW9uIG9mIHRoZSBmZWF0dXJlIHBsb3QgdGhhdCBvdXRwdXRzIGdlbmVzIGluIGl0YWxpY3MKCmBgYHtyfQppZiAodGlzc3VlID09ICJQQk1DIikgewogZ2VuZUNvZGVzIDwtIGMoInNjdF9QVFBSQyIsInNjdF9OS0c3Iiwic2N0X0NEMTQiLAogICAgICAgICAgICAgICAic2N0X0NEM0UiLCJzY3RfTUFSQ08iLCJzY3RfTFlaLTEiLAogICAgICAgICAgICAgICAic2N0X0NEMTkiLCJzY3RfTVM0QTEiLCJzY3RfU1RBQjIiLAogICAgICAgICAgICAgICAic2N0X0NENCIsInNjdF9DRDhBIiwgInNjdF9YQ0wxO1hDTDIiLAogICAgICAgICAgICAgICAic2N0X0NENUwiLCJzY3RfQzFRQiIsICJzY3RfTEVGMSIsCiAgICAgICAgICAgICAgICJzY3RfQUNUQTIiLCJzY3RfVldGIiwic2N0X0lHTEw1LTEiLAogICAgICAgICAgICAgICAic2N0X0NENjgiLCJzY3RfRkNHUjNBO0ZDR1IzQiIsInNjdF9UT1AyQSIpCiBnZW5lcyA8LSBjKCJQVFBSQyIsIk5LRzciLCJDRDE0IiwiQ0QzRSIsIk1BUkNPIiwiTFlaIiwKICAgICAgICAgICAiQ0QxOSIsIk1TNEExIiwiU1RBQjIiLCJDRDQiLCJDRDhBIiwgIlhDTDE7WENMMiIsCiAgICAgICAgICAgIkNENUwiLCJDMVFCIiwiTEVGMSIsIkFDVEEyIiwiVldGIiwiSUdMTDUiLCJDRDY4IiwKICAgICAgICAgICAiRkNHUjNBO0ZDR1IzQiIsIlRPUDJBIikgCn0gZWxzZSBpZiAodGlzc3VlID09ICJsaXZlciIpIHsKICBnZW5lQ29kZXMgPC0gYygic2N0X1B0cHJjIiwic2N0X0NBTENSTCIsInNjdF9OS0c3IiwKICAgICAgICAgICAgICAgICAic2N0X0NEM0UiLCJzY3RfTUFSQ08iLCJzY3RfTFlaLTEiLAogICAgICAgICAgICAgICAgICJzY3RfQ0QxOSIsInNjdF9NUzRBMSIsInNjdF9TVEFCMiIsCiAgICAgICAgICAgICAgICAgInNjdF9BTEItMSIsInNjdF9DRDQiLCJzY3RfQ0Q4QSIsCiAgICAgICAgICAgICAgICAgInNjdF9DTEVDNEciLCJzY3RfQ0Q1TCIsInNjdF9DMVFCIiwKICAgICAgICAgICAgICAgICAic2N0X0FDVEEyIiwic2N0X1ZXRiIsInNjdF9JR0xMNS0xIiwKICAgICAgICAgICAgICAgICAic2N0X0NENjgiLCJzY3RfWENMMTtYQ0wyIiwic2N0X0xFRjEiLAogICAgICAgICAgICAgICAgICJzY3RfUlNQTzMiLCJzY3RfTUVDT00iKQogIGdlbmVzIDwtIGMoIlBUUFJDIiwiQ0FMQ1JMIiwiTktHNyIsIkNEM0UiLCJNQVJDTyIsIkxZWiIsCiAgICAgICAgICAgICAiQ0QxOSIsIk1TNEExIiwiU1RBQjIiLCJBTEIiLCJDRDQiLCJDRDhBIiwKICAgICAgICAgICAgICJDTEVDNEciLCJDRDVMIiwiQzFRQiIsIkFDVEEyIiwiVldGIiwiSUdMTDUiLCJDRDY4IiwKICAgICAgICAgICAgICJYQ0wxO1hDTDIiLCJMRUYxIiwiUlNQTzMiLCJNRUNPTSIpIAp9CmZvcihudW0gaW4gMjpsZW5ndGgoZ2VuZUNvZGVzKSkgewogIHBsb3QgPC0gRmVhdHVyZVBsb3Qoc29iaiwKICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gZ2VuZUNvZGVzW251bV0pICsKICAgIGdndGl0bGUoYnF1b3RlKH5pdGFsaWMoLihnZW5lc1tudW1dKSkpKQogIHByaW50KHBsb3QpCiAgcGRmKHBhc3RlKCIuL2ZpZ3VyZXMvIiwgdGlzc3VlLCAiLyIsIHRpc3N1ZSwgIl8iLCBnZW5lc1tudW1dLCAiX1VNQVAucGRmIiwgc2VwID0gIiIpKQogIHByaW50KHBsb3QpCiAgZGV2Lm9mZigpCn0KYGBgCgojIyBDb3JyZWxhdGlvbiBwbG90cwoKR2VuZXJhdGUgaGVhdG1hcHMgY29tcGFyaW5nIHdvb2RjaHVjayBjbHVzdGVycyB3aXRoIHZhcmlvdXMgZGF0YXNldHMKCiMjIyBTZXR1cCBmb3IgYWxsIGNvcnJlbGF0aW9ucwoKRmlyc3QsIHNldCB1cCB3aGF0ZXZlciB3b29kY2h1Y2sgZGF0YXNldCBJIGFtIHdvcmtpbmcgd2l0aCBieSByZWFkaW5nIGluIHRoZSBvcnRob2xvZyB0YWJsZSwgY2hvb3Npbmcgd2hldGhlciB0aGUgb3J0aG9sb2dzIHRvIHVzZSBhcmUgaHVtYW4sIG1vdXNlLCBvciB3b29kY2h1Y2ssIGFuZCBjYWxjdWxhdGluZyB0aGUgYXZlcmFnZSBleHByZXNzaW9uIGZvciBlYWNoIGNsdXN0ZXIuIFRoZSBvdXRwdXQgb2YgdGhpcyBzZWN0aW9uIGlzIHRoZSBzY2FsZWQgY2x1c3RlciBnZW5lLWV4cHJlc3Npb24gbWF0cml4CgpgYGB7cn0KIyBSZWFkIGluIG9ydGhvbG9nIHRhYmxlCmdlbmVOYW1lVGFibGUgPC0gcmVhZC50YWJsZSgifi9Ecm9wYm94L1pvZS9zY2ZfdmVyc2lvbi9tYWtlX2d0Zi9vcnRob2ZpbmRlcl9zYzIvaG9tb2xvZ2VuZS9jb2xsZWN0ZWRPcnRob2ZpbmRlclBhaXJpbmdzLnRzdiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVFJVRSkKd29vZGNodWNrQ2x1c3RlckF2ZXJhZ2VzIDwtIEF2ZXJhZ2VFeHByZXNzaW9uKHNvYmosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheXMgPSAiU0NUIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsb3QgPSAic2NhbGUuZGF0YSIpCiMgU2NhbGUgZGF0YQp3b29kY2h1Y2tDbHVzdGVyQXZlcmFnZXMkU0NUIDwtIG5hLm9taXQodChzY2FsZSh0KHdvb2RjaHVja0NsdXN0ZXJBdmVyYWdlcyRTQ1QpKSkpCiMgR3JhYiBnZW5lIG5hbWVzIGZyb20gU2V1cmF0IG9iamVjdAp1bmlxdWVIaWVyIDwtIHJvdy5uYW1lcyh3b29kY2h1Y2tDbHVzdGVyQXZlcmFnZXMkU0NUKQp1bmlxdWVIaWVyIDwtIGFzLmRhdGEuZnJhbWUodW5pcXVlSGllcikKIyBCaW5kIHdpdGggZ2VuZU5hbWVUYWJsZSB0byBnZXQgY29ycmVjdCBvcmRlciAobm90aWNlIHVuaXF1ZUhpZXIgaXMgb24gbGVmdCkKbmV3TmFtZXMgPC0gZHBseXI6OmxlZnRfam9pbih1bmlxdWVIaWVyLCBnZW5lTmFtZVRhYmxlLCBieSA9ICJ1bmlxdWVIaWVyIikKIyBHZXQgb3J0aG9sb2dzIGZyb20gZWl0aGVyIG1vdXNlIG9yIGh1bWFuCnNwZWNpZXMgPC0gImh1bWFuIgppZiAoc3BlY2llcyA9PSAiaHVtYW4iKSB7CiAgIyBJZiBodW1hbiBvbmUtdG8tb25lIG9ydGhvIGhhcyBOQSwgcmVwbGFjZSB3aXRoIG1pa2Fkb19maW5hbF9zYzJfc3RyaW5nZW50X25vTWl0b19wcm90ZWluIGNvbHVtbgogICMgVGhpcyBpcyB0byBhdm9pZCBhbmQgcG90ZW50aWFsIG1pc3Rha2VzIGluIHJlY29nbml6aW5nIHRoaW5ncyBpdCBzaG91bGRuJ3QgYmUgcmVjb2duaXppbmcKICBuZXdOYW1lcyRzcGVjaWVzT25lVG9PbmUgPC0gaWZlbHNlKGlzLm5hKG5ld05hbWVzJGh1bWFuT25lVG9PbmUpLCBuZXdOYW1lcyR1bmlxdWVIaWVyLCBuZXdOYW1lcyRodW1hbk9uZVRvT25lKQp9IGVsc2UgaWYgKHNwZWNpZXMgPT0gIm1vdXNlIikgewogIG5ld05hbWVzJHNwZWNpZXNPbmVUb09uZSA8LSBpZmVsc2UoaXMubmEobmV3TmFtZXMkbW91c2VPbmVUb09uZSksIG5ld05hbWVzJHVuaXF1ZUhpZXIsIG5ld05hbWVzJG1vdXNlT25lVG9PbmUpCn0gZWxzZSBpZiAoc3BlY2llcyA9PSAid29vZGNodWNrIikgewogIG5ld05hbWVzJHNwZWNpZXNPbmVUb09uZSA8LSBuZXdOYW1lcyR1bmlxdWVIaWVyCn0KIyBHcmFiIGRhdGFmcmFtZQp3b29kY2h1Y2tDbHVzdGVyQXZlcmFnZXMgPC0gd29vZGNodWNrQ2x1c3RlckF2ZXJhZ2VzJFNDVAojIFJlcGxhY2UgbmFtZXMgd2l0aCBvbmUtdG8tb25lIG9ydGhvbG9ndWUgb2YgcGFydGljdWxhciBzcGVjaWVzCnJvdy5uYW1lcyh3b29kY2h1Y2tDbHVzdGVyQXZlcmFnZXMpIDwtIG5ld05hbWVzJHNwZWNpZXNPbmVUb09uZQojIE1ha2Ugc3VyZSBmb3JtYXR0ZWQgY29ycmVjdGx5Cndvb2RjaHVja0NsdXN0ZXJBdmVyYWdlcyA8LSBhcy5kYXRhLmZyYW1lKHdvb2RjaHVja0NsdXN0ZXJBdmVyYWdlcykKIyBPcmRlciBieSBnZW5lIG5hbWUKd29vZGNodWNrQ2x1c3RlckF2ZXJhZ2VzIDwtIHdvb2RjaHVja0NsdXN0ZXJBdmVyYWdlc1tvcmRlcihyb3cubmFtZXMod29vZGNodWNrQ2x1c3RlckF2ZXJhZ2VzKSksXQpgYGAKCiMjIyBEYXRhc2V0LXNwZWNpZmljIHNldHVwCgpDb3JyZWxhdGlvbiBvZiB3b29kY2h1Y2sgUEJNQ3Mgd2l0aCBodW1hbiA2OGsgUEJNQyBkYXRhc2V0IGZyb20gMTBYIEdlbm9taWNzCgpgYGB7cn0KIyBOb3cgbmVlZCB0byByZWFkIGluIDY4SyBQQk1DIGRhdGEKaHVtYW5QQk1DIDwtIHJlYWQuY3N2KCJ+L0Ryb3Bib3gvWm9lL3NjZl92ZXJzaW9uL2FuYWx5c2lzL2NvcnJlbGF0aW9uVGVzdHMvNjhLX3BibWNfZGF0YS82OEtfZW5yaWNoZWRHZW5lcy5jc3YiLAogICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gRkFMU0UpCiMgR2V0IHJpZCBvZiB0b3Agcm93Cmh1bWFuUEJNQyA8LSBodW1hblBCTUNbLTEsXQojIFNlcGFyYXRlIGFsbCBjZWxsIGFuZCBteWxvaWQgY2VsbCBkYXRhCmFsbENlbGxzIDwtIHNlbGVjdChodW1hblBCTUMsIFYxLCBWMiwgVjMpCm15ZWxvaWQgPC0gc2VsZWN0KGh1bWFuUEJNQywgVjUsIFY2LCBWNykKIyBSZW5hbWUgYW5kIGdldCByaWQgb2YgZmlyc3Qgcm93CmNvbG5hbWVzKGFsbENlbGxzKSA8LSBjKCJDbHVzdGVyIiwgIkdlbmUiLCAiRW5yaWNobWVudCIpCmNvbG5hbWVzKG15ZWxvaWQpIDwtIGMoIkNsdXN0ZXIiLCAiR2VuZSIsICJFbnJpY2htZW50IikKIyBHZXQgcmlkIG9mIHRvcCByb3cKYWxsQ2VsbHMgPC0gYWxsQ2VsbHNbLTEsXQpteWVsb2lkIDwtIG15ZWxvaWRbLTEsXQojIEkgY2FuIHRoZW4gZmlsdGVyIHRoZSByb3dzIGFuZCBiaW5kIHRoZSBkYXRhIGZyYW1lcyBiYWNrIHRvZ2V0aGVyIGJ5IGdlbmUgbmFtZQpmb3IgKGogaW4gMToxMCkgewogIGRhdCA8LSBkcGx5cjo6ZmlsdGVyKGFsbENlbGxzLCBDbHVzdGVyID09IGopCiAgI2RhdCA8LSBnZXQocGFzdGUoImFsbENlbGxzIiwgaiwgc2VwID0gIiIpKQogICMgTXVsdGlwbHkgZW5yaWNobWVudCB2YWx1ZXMgYnkgLTEgYmVjYXVzZSB0aGUgc2lnbnMgYXJlIGJhY2t3YXJkcz8/PwogIGRhdCRFbnJpY2htZW50IDwtIGFzLm51bWVyaWMoZGF0JEVucmljaG1lbnQpICogLTEKICBkYXQgPC0gZHBseXI6OnNlbGVjdChkYXQsIEdlbmUsIEVucmljaG1lbnQpCiAgY29sbmFtZXMoZGF0KSA8LSBjKCJHZW5lIiwgcGFzdGUoIkVucmljaG1lbnQiLCBqLCBzZXAgPSAiIikpCiAgaWYgKGogPT0gMSkgewogICAgYWxsQ2VsbHNNYXRyaXggPC0gZGF0CiAgfQogIGVsc2UgewogICAgYWxsQ2VsbHNNYXRyaXggPC0gZHBseXI6OmZ1bGxfam9pbihhbGxDZWxsc01hdHJpeCwgZGF0LCBieSA9ICJHZW5lIikKICB9Cn0KIyBNYWtlIHJvdyBuYW1lcyBnZW5lIG5hbWVzCnJvd25hbWVzKGFsbENlbGxzTWF0cml4KSA8LSBhbGxDZWxsc01hdHJpeCRHZW5lCmFsbENlbGxzTWF0cml4IDwtIGRwbHlyOjpzZWxlY3QoYWxsQ2VsbHNNYXRyaXgsIC1HZW5lKQojIE1ha2UgY29sdW1uIG5hbWVzIGNlbGwgdHlwZXMKY29sbmFtZXMoYWxsQ2VsbHNNYXRyaXgpIDwtIGMoIkFjdGl2YXRlZCBDRDgrIiwgIk5haXZlIENEOCsiLCAiTWVtb3J5IGFuZCBSZWcgVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJOYWl2ZSBDRDQrIiwgIk5LIiwgIkNEOCsiLCAiQiIsICJNZWdha2FyeW9jeXRlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJNb25vY3l0ZXMgYW5kIERlbmRyaXRpYyIsICJCLCBEZW5kcml0aWMsIFQiKQojIFNjYWxlIGFjcm9zcyBjb2x1bW5zCmFsbENlbGxzTWF0cml4IDwtIHQoc2NhbGUodChhbGxDZWxsc01hdHJpeCkpKQojIE9yZGVyIGdlbmVzIGFscGhhYmV0aWNhbGx5IGJ5IGdlbmUgbmFtZQphbGxDZWxsc01hdHJpeCA8LSBhbGxDZWxsc01hdHJpeFtvcmRlcihyb3cubmFtZXMoYWxsQ2VsbHNNYXRyaXgpKSxdCmBgYAoKQ29ycmVsYXRpb24gb2Ygd29vZGNodWNrIGxpdmVyIHdpdGggaHVtYW4gbGl2ZXIgZGF0YXNldCBmcm9tIE1hY1BhcmxhbmQgKmV0IGFsLiogKDIwMTgpCgpgYGB7cn0KIyBGaW5kIGNsdXN0ZXIgYXZlcmFnZXMgb2YgaHVtYW4gbGl2ZXIgZGF0YQpsb2FkKCJ+L0Ryb3Bib3gvWm9lL3NjZl92ZXJzaW9uL2FuYWx5c2lzL2NvcnJlbGF0aW9uVGVzdHMvSHVtYW5MaXZlci5SRGF0YSIpCiMgUnVuIFNDVHJhbnNmb3JtCkh1bWFuTGl2ZXJTZXVyYXQgPC0gVXBkYXRlU2V1cmF0T2JqZWN0KEh1bWFuTGl2ZXJTZXVyYXQpCkh1bWFuTGl2ZXJTZXVyYXQgPC0gU0NUcmFuc2Zvcm0oSHVtYW5MaXZlclNldXJhdCkKaHVtYW5DbHVzdGVyQXZlcmFnZXMgPC0gQXZlcmFnZUV4cHJlc3Npb24oSHVtYW5MaXZlclNldXJhdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXNzYXlzID0gIlNDVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsb3QgPSAic2NhbGUuZGF0YSIpCiMgUmVwbGFjZSBjbHVzdGVyIG51bWJlcnMgd2l0aCBuYW1lcwpjb2xuYW1lcyhodW1hbkNsdXN0ZXJBdmVyYWdlcyRTQ1QpIDwtIGMoIkhlcCAxIiwgIkFscGhhLWJldGEgVCBjZWxscyIsICJIZXAgMiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSW5mbGFtbWF0b3J5IG1hY3MiLCAiSGVwIDMiLCAiSGVwIDQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBsYXNtYSBjZWxscyIsICJOSy1saWtlIGNlbGxzIiwgIkdhbW1hLWRlbHRhIFQgY2VsbHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5vbi1pbmZsYW1tYXRvcnkgbWFjcyIsICJQZXJpcG9ydGFsIExTRUNzIiwgIkNlbnRyYWwgdmVub3VzIExTRUNzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQb3J0YWwgZW5kb3RoZWxpYWwgY2VsbHMiLCAiSGVwIDUiLCAiSGVwIDYiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk1hdHVyZSBCIGNlbGxzIiwgIkNob2xhbmdpb2N5dGVzIiwgIkdhbW1hLWRlbHRhIFQgY2VsbHMgMiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRXJ5dGhyb2lkIGNlbGxzIiwgIkhlcGF0aWMgc3RlbGxhdGUgY2VsbHMiKQojIElmIG9ubHkgbG9va2luZyBhdCBzcGVjaWZpYyBjbHVzdGVycwojaHVtYW5DbHVzdGVyQXZlcmFnZXMkU0NUIDwtIGh1bWFuQ2x1c3RlckF2ZXJhZ2VzJFNDVFssYygiMyIsIjEiLCIxNSIsIjYiLCIxNCIsIjUiKV0KIyBPdGhlcndpc2UgZ28gc3RyYWlnaHQgdG8gaGVyZToKaHVtYW5DbHVzdGVyQXZlcmFnZXMkU0NUIDwtIG5hLm9taXQodChzY2FsZSh0KGh1bWFuQ2x1c3RlckF2ZXJhZ2VzJFNDVCkpKSkKIyBHcmFiIGdlbmUgbmFtZXMKaHVtYW5HZW5lcyA8LSByb3cubmFtZXMoaHVtYW5DbHVzdGVyQXZlcmFnZXMkU0NUKQojIE5vdyB0dXJuIGludG8gbGFyZ2UgZGF0YWZyYW1lCmFsbENlbGxzTWF0cml4IDwtIGFzLmRhdGEuZnJhbWUoaHVtYW5DbHVzdGVyQXZlcmFnZXMkU0NUKQojIE9yZGVyIGJ5IHJvdyBuYW1lCmFsbENlbGxzTWF0cml4IDwtIGFsbENlbGxzTWF0cml4W29yZGVyKHJvdy5uYW1lcyhhbGxDZWxsc01hdHJpeCkpLF0KYGBgCgpDb3JyZWxhdGlvbiBvZiB3b29kY2h1Y2sgbGl2ZXIgd2l0aCBodW1hbiBsaXZlciBkYXRhc2V0IGZyb20gQWl6YXJhbmkgKmV0IGFsLioKCmBgYHtyfQojIFJlYWQgaW4gQWl6YXJhbmkgZGF0YXNldAphaXphcmFuaSA8LSByZWFkUkRTKCJ+L0Ryb3Bib3gvWm9lL3NjZl92ZXJzaW9uL2FuYWx5c2lzL2NvcnJlbGF0aW9uVGVzdHMvR1NFMTI0Mzk1X05vcm1hbGh1bWFubGl2ZXJkYXRhLlJEYXRhIikKIyBSZWFkIGluIGNsdXN0ZXJzIGFuZCBsYWJlbCBjZWxscwphaXphcmFuaUNsdXN0ZXJzIDwtIHJlYWQudGFibGUoIn4vRHJvcGJveC9ab2Uvc2NmX3ZlcnNpb24vYW5hbHlzaXMvY29ycmVsYXRpb25UZXN0cy9HU0UxMjQzOTVfY2x1c3RlcnBhcnRpdGlvbi50eHQiKQojIE9ubHkga2VlcCBjZWxscyBpbiB0aGUgY2x1c3RlciBvYmplY3QKYWl6YXJhbmkgPC0gYWl6YXJhbmlbLGludGVyc2VjdChjb2xuYW1lcyhhaXphcmFuaSkscm93Lm5hbWVzKGFpemFyYW5pQ2x1c3RlcnMpKV0KIyBDcmVhdGUgU2V1cmF0IG9iamVjdAphaXphcmFuaSA8LSBDcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gYWl6YXJhbmkpCiMgUnVuIFNDVHJhbnNmb3JtCmFpemFyYW5pIDwtIFNDVHJhbnNmb3JtKGFpemFyYW5pKQojIEFkZCBjbHVzdGVyIElEcwpJZGVudHMoYWl6YXJhbmkpIDwtIGFpemFyYW5pQ2x1c3RlcnMkc2N0LmNwYXJ0CiMgR2V0IGNsdXN0ZXIgYXZlcmFnZXMKYWl6YXJhbmlBdmVyYWdlcyA8LSBBdmVyYWdlRXhwcmVzc2lvbihhaXphcmFuaSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheXMgPSAiU0NUIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbG90ID0gInNjYWxlLmRhdGEiKQphaXphcmFuaUF2ZXJhZ2VzJFNDVCA8LSBuYS5vbWl0KHQoc2NhbGUodChhaXphcmFuaUF2ZXJhZ2VzJFNDVCkpKSkKIyBHcmFiIGdlbmUgbmFtZXMKYWl6YXJhbmlHZW5lcyA8LSByb3cubmFtZXMoYWl6YXJhbmlBdmVyYWdlcyRTQ1QpCiMgTm93IHR1cm4gaW50byBsYXJnZSBkYXRhZnJhbWUKYWxsQ2VsbHNNYXRyaXggPC0gYXMuZGF0YS5mcmFtZShhaXphcmFuaUF2ZXJhZ2VzJFNDVCkKIyBPcmRlciBieSByb3cgbmFtZQphbGxDZWxsc01hdHJpeCA8LSBhbGxDZWxsc01hdHJpeFtvcmRlcihyb3cubmFtZXMoYWxsQ2VsbHNNYXRyaXgpKSxdCmBgYAoKQ29ycmVsYXRpb24gb2Ygd29vZGNodWNrIGxpdmVyIHdpdGggd29vZGNodWNrIFBCTUNzLiBGb3IgdGhpcyBjb3JyZWxhdGlvbiwgcmVhZCBpbiB0aGUgd29vZGNodWNrIGxpdmVyIGRhdGFzZXQgYXQgdGhlIGJlZ2lubmluZyBvZiB0aGlzIHNjcmlwdCBhbmQgdGhlbiByZWFkIGluIHRoZSB3b29kY2h1Y2sgUEJNQ3MgYmVsb3cKCmBgYHtyfQojIFN0YXJ0IHdpdGggbGl2ZXIgYW5kIHJlYWQgaW4gd29vZGNodWNrIFBCTUNzIGFnYWluCmxvYWQoIn4vRHJvcGJveC9ab2Uvc2NmX3ZlcnNpb24vYW5hbHlzaXMvaGVhbHRoeV9zYy9zZXVyYXRfb2JqZWN0cy9ub19kcm9wbGV0UUMvaW50ZWdyYXRlZF9QQk1DX2NjYV9rYW5jaG9yNV9zY0NsdXN0Vml6LlJEYXRhIikKSWRlbnRzKHNjU2V1cmF0KSA8LSAiaW50ZWdyYXRlZF9zbm5fcmVzLjAuNiIKIyBGaW5kIGNsdXN0ZXIgYXZlcmFnZXMKcGJtY0NsdXN0ZXJBdmVyYWdlcyA8LSBBdmVyYWdlRXhwcmVzc2lvbihzY1NldXJhdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheXMgPSAiU0NUIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbG90ID0gInNjYWxlLmRhdGEiKQpwYm1jQ2x1c3RlckF2ZXJhZ2VzIDwtIGFzLmRhdGEuZnJhbWUobmEub21pdCh0KHNjYWxlKHQocGJtY0NsdXN0ZXJBdmVyYWdlcyRTQ1QpKSkpKQojIE9yZGVyIGJ5IHJvdyBuYW1lCmFsbENlbGxzTWF0cml4IDwtIHBibWNDbHVzdGVyQXZlcmFnZXNbb3JkZXIocm93Lm5hbWVzKHBibWNDbHVzdGVyQXZlcmFnZXMpKSxdCmBgYAoKIyMjIE91dHB1dCBwbG90cyBmb3IgYWxsIGNvcnJlbGF0aW9ucwoKYGBge3J9CnNwZWNpZXNEYXRhIDwtICJIdW1hbiBMaXZlciAoQWl6YXJhbmkgZXQgYWwuKSIKd29vZGNodWNrRGF0YSA8LSAiV29vZGNodWNrIExpdmVyIgojIE5vdyBmaW5kIGludGVyc2VjdGluZyBnZW5lcwptYXRjaGVzIDwtIGludGVyc2VjdChyb3cubmFtZXMoYWxsQ2VsbHNNYXRyaXgpLAogICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMod29vZGNodWNrQ2x1c3RlckF2ZXJhZ2VzKSkKIyBMb29rIGF0IGhvdyBtYW55IGdlbmVzIG1hdGNoZWQKbGVuZ3RoKG1hdGNoZXMpCiMgTWFrZSBuZXcgbWF0cmljZXMgd2l0aCBvbmx5IG1hdGNoaW5nIGdlbmUgbmFtZXMKdG9Db3IgPC0gYWxsQ2VsbHNNYXRyaXhbbWF0Y2hlcyxdCndvb2RjaHVja0F2ZXJhZ2VzQ29yIDwtIHdvb2RjaHVja0NsdXN0ZXJBdmVyYWdlc1ttYXRjaGVzLF0KIyBEbyBQZWFyc29uCnBlYXJWYWwgPC0gY29yKHRvQ29yLCB3b29kY2h1Y2tBdmVyYWdlc0NvciwgbWV0aG9kID0gInBlYXJzb24iKQpoZWF0bWFwKHBlYXJWYWwsCiAgICAgICAgbWFpbiA9IHBhc3RlKCJQZWFyc29uIGNvcnJlbGF0aW9uIG9mIiwgc3BlY2llc0RhdGEsICJ2cyIsIHdvb2RjaHVja0RhdGEpLAogICAgICAgIHhsYWIgPSB3b29kY2h1Y2tEYXRhLAogICAgICAgIHlsYWIgPSBzcGVjaWVzRGF0YSkKICAgICAgICAjbWFyZ2lucyA9IGMoNiwxMSkpCiNSb3d2ID0gTkEsCiNDb2x2ID0gTkEpCiMgRG8gU3BlYXJtYW4Kc3BlYXJWYWwgPC0gY29yKHRvQ29yLCB3b29kY2h1Y2tBdmVyYWdlc0NvciwgbWV0aG9kID0gInNwZWFybWFuIikKaGVhdG1hcChzcGVhclZhbCwKICAgICAgICBtYWluID0gcGFzdGUoIlNwZWFybWFuIGNvcnJlbGF0aW9uIG9mIiwgc3BlY2llc0RhdGEsICJ2cyIsIHdvb2RjaHVja0RhdGEpLAogICAgICAgIHhsYWIgPSB3b29kY2h1Y2tEYXRhLAogICAgICAgIHlsYWIgPSBzcGVjaWVzRGF0YSkKICAgICAgICAjbWFyZ2lucyA9IGMoNiwxMSkpCiNSb3d2ID0gTkEsCiNDb2x2ID0gTkEpCmBgYAoKCgoK